package com.wiscamp.ninechapters.common.persistence;

import com.wiscamp.ninechapters.common.core.CollectionHelper;
import com.wiscamp.ninechapters.common.rest.PagedEntity;
import com.wiscamp.ninechapters.common.rest.Pagination;
import com.wiscamp.ninechapters.ddd.PagedEntities;
import com.wiscamp.ninechapters.ddd.PagedQuery;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.validation.annotation.Validated;

import java.security.InvalidParameterException;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.function.*;

/**
 * Persistence Helper
 *
 * @author yifeng
 */
@Validated
public class DbHelper {
    private DbHelper() {
    }

    // region persistence toolkit version 2.0
    public static <T> T findById(@Min(0) long id, LongFunction<Optional<T>> function) {
        Optional<T> optionalPO = function.apply(id);
        return optionalPO.orElse(null);
    }

    public static <T> T getById(@Min(0) long id, LongFunction<T> function) {
        return function.apply(id);
    }

    public static <T> T getByText(@NotBlank String text, Function<String, T> function) {
        return function.apply(text);
    }

    public static <T> T getByIdAndId(@Min(0) long id1, @Min(0) long id2, BiFunction<Long, Long, T> function) {
        return function.apply(id1, id2);
    }

    public static <T> T getByTextAndId(@NotBlank String text, @Min(0) long id, BiFunction<String, Long, T> function) {
        return function.apply(text, id);
    }

    public static <T> Collection<T> getListById(@Min(0) long id, LongFunction<Collection<T>> function) {
        return function.apply(id);
    }

    public static <T> Collection<T> getListByIdAndId(@Min(0) long id1, @Min(0) long id2, BiFunction<Long, Long, Collection<T>> function) {
        return function.apply(id1, id2);
    }

    public static <T> Collection<T> getListByText(@NotBlank String text, Function<String, Collection<T>> function) {
        return function.apply(text);
    }

    public static <T> Collection<T> getListByIds(@NotBlank Collection<Long> ids, Function<Collection<Long>, Collection<T>> function) {
        return function.apply(ids);
    }

    public static <T> Collection<T> getAll(Supplier<Collection<T>> supplier) {
        return supplier.get();
    }

    public static <T> T save(@NotNull T po, UnaryOperator<T> function) {
        return function.apply(po);
    }

    public static <T> Collection<T> saveAll(@NotBlank Collection<T> poList,
                                            UnaryOperator<Collection<T>> function) {
        return function.apply(poList);
    }

    public static boolean delete(@Min(0) long id, LongConsumer consumer) {
        consumer.accept(id);
        return true;
    }

    public static boolean logicallydelete(@Min(0) long id, LongConsumer consumer) {
        consumer.accept(id);
        return true;
    }

    public static <T> boolean delete(@NotNull T po, Consumer<T> consumer) {
        consumer.accept(po);
        return true;
    }

    public static boolean enable(@Min(0) long id, LongConsumer consumer) {
        consumer.accept(id);
        return true;
    }

    public static boolean disable(@Min(0) long id, LongConsumer consumer) {
        consumer.accept(id);
        return true;
    }
    // endregion


    // region persistence toolkit version : Entity
    public static <T, R> R findEntityById(@Min(0) long id, LongFunction<Optional<T>> findFunction, Function<T, R> converterFunction) {
        var po = findById(id, findFunction);
        if (Objects.nonNull(po))
            return converterFunction.apply(po);
        return null;
    }

    public static <T, R> R getEntityById(@Min(0) long id, LongFunction<T> getFunction, Function<T, R> converterFunction) {
        var po = getById(id, getFunction);
        if (Objects.nonNull(po))
            return converterFunction.apply(po);
        return null;
    }

    public static <T, R> R getEntityByText(@NotBlank String text, Function<String, T> getFunction, Function<T, R> converterFunction) {
        var po = getByText(text, getFunction);
        if (Objects.nonNull(po))
            return converterFunction.apply(po);
        return null;
    }

    public static <T, R> R getEntityByIdAndId(@Min(0) long id1, @Min(0) long id2, BiFunction<Long, Long, T> getFunction, Function<T, R> converterFunction) {
        var po = getByIdAndId(id1, id2, getFunction);
        if (Objects.nonNull(po))
            return converterFunction.apply(po);
        return null;
    }

    public static <T, R> R getEntityByTextAndId(@NotBlank String text, @Min(0) long id, BiFunction<String, Long, T> getFunction, Function<T, R> converterFunction) {
        var po = getByTextAndId(text, id, getFunction);
        if (Objects.nonNull(po))
            return converterFunction.apply(po);
        return null;
    }

    public static <T, R> Collection<R> getEntitiesById(@Min(0) long id, LongFunction<Collection<T>> function, Function<T, R> converterFunction) {
        var poList = getListById(id, function);
        return CollectionHelper.convertToArrayList(poList, converterFunction);
    }

    public static <T, R> Collection<R> getEntitiesByIds(@NotBlank Collection<Long> ids,
                                                        Function<Collection<Long>, Collection<T>> function, Function<T, R> converterFunction) {
        var poList = getListByIds(ids, function);
        return CollectionHelper.convertToArrayList(poList, converterFunction);
    }

    public static <T, R> Collection<R> getEntitiesByText(@NotBlank String text, Function<String, Collection<T>> getFunction, Function<T, R> converterFunction) {
        var poList = getListByText(text, getFunction);
        return CollectionHelper.convertToArrayList(poList, converterFunction);
    }

    public static <T, R> Collection<R> getEntitiesByIdAndId(@NotBlank long id1, @Min(0) long id2,
                                                            BiFunction<Long, Long, Collection<T>> function, Function<T, R> converterFunction) {
        var poList = getByIdAndId(id1, id2, function);
        return CollectionHelper.convertToArrayList(poList, converterFunction);
    }

    public static <T, R> Collection<R> getEntitiesByTextAndId(@NotBlank String text, @Min(0) long id,
                                                              BiFunction<String, Long, Collection<T>> function, Function<T, R> converterFunction) {
        var poList = getByTextAndId(text, id, function);
        return CollectionHelper.convertToArrayList(poList, converterFunction);
    }

    public static <T, R> Collection<R> getAllEntities(Supplier<Collection<T>> supplier, Function<T, R> converterFunction) {
        var poList = getAll(supplier);
        return CollectionHelper.convertToArrayList(poList, converterFunction);
    }

    public static <T, R> R saveEntity(@NotNull R entity, UnaryOperator<T> saveFunction, Function<R, T> toPOFunction, Function<T, R> toEntityFunction) {
        var po = toPOFunction.apply(entity);
        var savedPO = save(po, saveFunction);
        if (Objects.nonNull(savedPO))
            return toEntityFunction.apply(savedPO);
        return null;
    }

    public static <T, R> Collection<R> saveEntities(@NotNull Collection<R> entities, UnaryOperator<Collection<T>> saveFunction, Function<R, T> toPOFunction, Function<T, R> toEntityFunction) {
        var poList = CollectionHelper.convertToArrayList(entities, toPOFunction);
        var savedPOList = save(poList, saveFunction);
        return CollectionHelper.convertToArrayList(savedPOList, toEntityFunction);
    }
    // endregion


    // region paged entities

    // 注意：此处Entity非业务实体，依旧是PO对象。 （考虑到兼容旧接口，暂时不重构）
    private static <T> void checkPagination(Pagination<T> pagination) {
        if (pagination == null) {
            throw new InvalidParameterException("The pagination parameter cannot be empty.");
        } else {
            T query = pagination.getQuery();
            if (query == null) {
                throw new InvalidParameterException("The custom query object cannot be empty.");
            }
        }
    }

    private static <T, K> PagedEntity<T> getPagedEntitiesBySort(Pagination<K> pagination, Specification<T> specification,
                                                                String primaryField, BiFunction<Specification<T>, Pageable, Page<T>> function,
                                                                Sort.Direction direction) {
        checkPagination(pagination);
        var sort = Sort.by(direction, primaryField);
        int pagerNumber = pagination.getPage() <= 0 ? 1 : pagination.getPage();
        int limit = pagination.getPerpage() <= 0 ? 1 : pagination.getPerpage();
        var pageable = PageRequest.of(pagerNumber - 1, limit, sort);
        Page<T> page = function.apply(specification, pageable);
        return new PagedEntity<>(page.getTotalPages(), (int) page.getTotalElements(),
                direction.toString(), primaryField, page.getContent());
    }

    public static <T, K> PagedEntity<T> getPagedEntitiesByDESC(Pagination<K> pagination, Specification<T> specification,
                                                               String primaryField, BiFunction<Specification<T>, Pageable, Page<T>> function) {
        return getPagedEntitiesBySort(pagination, specification, primaryField, function, Sort.Direction.DESC);
    }

    public static <T, K> PagedEntity<T> getPagedEntitiesByASC(Pagination<K> pagination, Specification<T> specification,
                                                              String primaryField, BiFunction<Specification<T>, Pageable, Page<T>> function) {
        return getPagedEntitiesBySort(pagination, specification, primaryField, function, Sort.Direction.ASC);
    }

    public static <T1, T2> PagedEntity<T2> convertPagedEntity(PagedEntity<T1> source, Function<T1, T2> function) {
        var target = new PagedEntity<T2>();
        target.setField(source.getField());
        target.setSort(source.getSort());
        target.setTotal(source.getTotal());
        target.setPages(source.getPages());
        var targetList = CollectionHelper.convertToArrayList(source.getEntities(), function);
        target.setEntities(targetList);
        return target;
    }
    // endregion


    // region paged query
    public static <T, K> PagedEntities<T> getEntitiesByPagedQuery(PagedQuery pagedQuery, Specification<T> specification,
                                                                  String sortProperty, BiFunction<Specification<T>, Pageable, Page<T>> function,
                                                                  Sort.Direction direction) {
        var sort = Sort.by(direction, sortProperty);
        int pagerNumber = pagedQuery.getPage() <= 0 ? 1 : pagedQuery.getPage();
        int limit = pagedQuery.getPageSize() <= 0 ? 1 : pagedQuery.getPageSize();
        var pageable = PageRequest.of(pagerNumber - 1, limit, sort);
        Page<T> page = function.apply(specification, pageable);
        return new PagedEntities<>((int) page.getTotalElements(), pagedQuery.getPageSize(),
                page.getContent(), pagedQuery.getPage(), pagedQuery.getSortDirection().name(), pagedQuery.getSortProperty());
    }

    public static <T1, T2> PagedEntities<T2> convertPagedEntities(PagedEntities<T1> source, Function<T1, T2> function) {
        var targetList = CollectionHelper.convertToArrayList(source.getEntities(), function);
        var target = new PagedEntities<T2>(source.getTotalCount(), source.getPageSize(), targetList, source.getPage(), source.getSortDirection(), source.getSortProperty());
        return target;
    }

    // endregion
}
